#include "tile/solidtile.hpp"

#include "actor.hpp"
#include "game.hpp"
#include "graphics.hpp"
#include "tile/tilemap.hpp"

namespace nmc
{
	SolidTile::SolidTile(const Point& tileMapPosition, TileMap* tileMap)
		:Tile(Tile::SOLID, tileMapPosition, tileMap),
		mSolidTop(true),
		mSolidRight(true),
		mSolidBottom(true),
		mSolidLeft(true),
		mConnectedToASlopingRightTile(false),
		mConnectedToASlopingLeftTile(false),
        mSlopingLeftTilePusher(false)
	{

	}

	void SolidTile::stickToFloor(Actor* actor, Point& velocity)
	{
        const Point actorBCP = actor->getBottomCollisionPoint();

        if (actorBCP.y < getWorldPosition().y)
        {
            velocity.y = getWorldPosition().y - actorBCP.y - 1;
        }
        else
        {
            velocity.y = actorBCP.y  - getWorldPosition().y + 1;
        }
	}

	void SolidTile::resolveCollision(Actor* actor, Point& velocity)
	{
        Point futurePosition = actor->getPosition() + velocity;
        
		// Check if the actor will be anywhere near the solid tile.
		// If it's not then we know we don't have a collision.
		if (futurePosition.x >= getWorldPosition().x + getWidth() 
			|| futurePosition.x + actor->getWidth() <= getWorldPosition().x 
			|| futurePosition.y >= getWorldPosition().y + getHeight()
			|| futurePosition.y + actor->getHeight() <= getWorldPosition().y)
		{
			return;
		}

        Point actorBCP = actor->getBottomCollisionPoint();

        if (mSlopingLeftTilePusher
            && actorBCP.x + velocity.x > getWorldPosition().x
            && actorBCP.y + velocity.y > getWorldPosition().y
            && actorBCP.x + velocity.x <= getWorldPosition().x + getWidth()
            && actorBCP.y + velocity.y <= getWorldPosition().y + getHeight())
        {
            int yCorrection = ((actorBCP.x + velocity.x) - getWorldPosition().x) - (getWorldPosition().y - (actorBCP.y + velocity.y)) + 1;
            velocity.y = -yCorrection; 
            return;
        }

		// If the solid tile is connected to a sloping left tile and more than
		// half of the actor is left of this tile (that is more than half of
		// the actor is in the area of the sloping left tile), then this tile
		// shouldn't act as solid as collision will be handled by the sloping left
		// tile.
		if (mConnectedToASlopingLeftTile
			&& getWorldPosition().x > actor->getBottomCollisionPoint().x)
		{
			return;
		}

		// The same thing as above, if the solid tile is connected to a sloping left tile,
		// applies if the solid tile is connected to a sloping right tile, that is if more 
		// than half of the actor is in the area of the sloping right tile the sloping right
		// tile will handle the collision.
		if (mConnectedToASlopingRightTile
			&& getWorldPosition().x + getWidth() <= actor->getBottomCollisionPoint().x
            && getWorldPosition().y + getHeight() >= actor->getBottomCollisionPoint().y + velocity.y)
		{
			return;
		}

		// It's very important that collision of the top only
		// occurs if the actor is above the tile and tries to move
		// down into the tile.
		if (mSolidTop
			&& actor->getPosition().y + actor->getHeight() <= getWorldPosition().y // Actor is above the tile
			&& actor->getPosition().y + velocity.y + actor->getHeight() > getWorldPosition().y) // And will be in the tile.
		{
			int yCorrection =  actor->getPosition().y + velocity.y + actor->getHeight() - getWorldPosition().y;
			velocity.y -= yCorrection;
			return;
		}

		// It's very important that collision of the right only
		// occurs if the actor is left of the tile and tries to move
		// to the right into the tile.
		if (mSolidRight
			&& velocity.x < 0 // Actor is moving to the left.
			&& actor->getPosition().x  >= getWorldPosition().x + getWidth() // Actor right of the tile.
			&& getWorldPosition().x + getWidth() > actor->getPosition().x + velocity.x) // Actor will be in the tile.
		{
			int xCorrection = getWorldPosition().x + getWidth() - (actor->getPosition().x + velocity.x);
			velocity.x += xCorrection;
			return;
		}

		// It's very important that collision of the left only
		// occurs if the actor is right of the tile and tries to move
		// to the left into the tile.
		if (mSolidLeft
			&& velocity.x > 0 // Actor is moving to the right.
			&& actor->getPosition().x  + actor->getWidth() <= getWorldPosition().x // Actor is left of the tile.
			&& getWorldPosition().x < actor->getPosition().x + velocity.x + actor->getWidth()) // Actor will be in the tile.
		{
			int xCorrection = actor->getPosition().x + velocity.x + actor->getWidth() - getWorldPosition().x;
			velocity.x -= xCorrection;
			return;
		}

		// It's very important that collision of the bottom only
		// occurs if the actor is below the tile and tries to move
		// up into the tile.
		if (mSolidBottom
			&& velocity.y < 0 // Actor is moving upwards.
			&& actor->getPosition().y >= getWorldPosition().y + getHeight() // Actor is below the tile.
			&& getWorldPosition().y + getHeight() > actor->getPosition().y + velocity.y) // Actor will be in the tile.
		{
			int yCorrection = getWorldPosition().y + getHeight() - (actor->getPosition().y + velocity.y);
			velocity.y += yCorrection;
			return;
		}
	}

    bool SolidTile::isStandingOnFloor(Actor* actor)
	{
        return (actor->getPosition().x + actor->getWidth() - 1) >= getWorldPosition().x
            && actor->getPosition().x <= getWorldPosition().x + getWidth() - 1
            && actor->getBottomCollisionPoint().y == getWorldPosition().y - 1;
	}

	void SolidTile::calculateSolidity(TileMap* tileMap)
	{
		Tile* top = tileMap->getTile(getTileMapPosition().x, 
									 getTileMapPosition().y - 1);
		Tile* right = tileMap->getTile(getTileMapPosition().x + 1,
									   getTileMapPosition().y);
		Tile* left = tileMap->getTile(getTileMapPosition().x - 1, 
									  getTileMapPosition().y);
		Tile* bottom = tileMap->getTile(getTileMapPosition().x,
										getTileMapPosition().y + 1);

		mSolidTop = top->getType() != Tile::SOLID
			&& top->getType() != Tile::SLOPING_LEFT
			&& top->getType() != Tile::SLOPING_RIGHT;
		
		mSolidRight = right->getType() != Tile::SOLID
			&& right->getType() != Tile::SLOPING_RIGHT;

		mSolidBottom = bottom->getType() != Tile::SOLID;

		mSolidLeft = left->getType() != Tile::SOLID
			&& left->getType() != Tile::SLOPING_LEFT;

		mConnectedToASlopingRightTile = right->getType() == Tile::SLOPING_RIGHT;
		mConnectedToASlopingLeftTile = left->getType() == Tile::SLOPING_LEFT;

        mSlopingLeftTilePusher = (top->getType() == Tile::SLOPING_LEFT)
            && (left->getType() == Tile::SLOPING_LEFT);
	}

	void SolidTile::draw(Graphics* graphics)
	{

	}

	void SolidTile::drawType(Graphics* graphics)
	{
		graphics->setBlendMode(Graphics::ALPHA);

        if (mSlopingLeftTilePusher)
        {
            graphics->setColor(Color(0x0000ff));
            graphics->fillRectangle(Rectangle(getWorldPosition().x + Tile::SIZE / 2 - 2,
                                              getWorldPosition().y + Tile::SIZE / 2 - 2,
                                              3,
                                              3));
        }

        graphics->setColor(Color(0xffff80));
		graphics->drawRectangle(Rectangle(getWorldPosition().x, 
										  getWorldPosition().y, 
										  Tile::SIZE, 
										  Tile::SIZE));
	}
	
	void SolidTile::drawSolidity(Graphics* graphics)
	{
		graphics->setBlendMode(Graphics::ALPHA);

        graphics->setColor(Color(0xff0000));

		if (mSolidBottom)
		{
			graphics->drawLine(getWorldPosition().x, 
							   getWorldPosition().y + Tile::SIZE - 1, 
							   getWorldPosition().x + Tile::SIZE - 1,
							   getWorldPosition().y + Tile::SIZE - 1);
		}

        graphics->setColor(Color(0xff0000));

		if (mSolidLeft)
		{
			graphics->drawLine(getWorldPosition().x, 
							   getWorldPosition().y, 
							   getWorldPosition().x,
							   getWorldPosition().y + Tile::SIZE - 1);
		}

        graphics->setColor(Color(0xff0000));

		if (mSolidRight)
		{
			graphics->drawLine(getWorldPosition().x + Tile::SIZE - 1, 
							   getWorldPosition().y, 
							   getWorldPosition().x + Tile::SIZE - 1,
							   getWorldPosition().y + Tile::SIZE - 1);
		}

        graphics->setColor(Color(0x00ff00));

		if (mSolidTop)
		{
			graphics->drawLine(getWorldPosition().x, 
							   getWorldPosition().y, 
							   getWorldPosition().x + Tile::SIZE - 1,
							   getWorldPosition().y);
		}

		Tile::drawSolidity(graphics);
	}
}